// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <float.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>

#include <cobalt-client/cpp/histogram-internal.h>
#include <cobalt-client/cpp/histogram.h>
#include <cobalt-client/cpp/metric-options.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <fbl/string.h>
#include <fbl/string_printf.h>
#include <fuchsia/cobalt/c/fidl.h>
#include <lib/sync/completion.h>
#include <lib/zx/time.h>
#include <unittest/unittest.h>

#include "fake_logger.h"

namespace cobalt_client {
namespace internal {
namespace {

// Number of threads for running multi threaded tests.
constexpr uint64_t kThreads = 20;

// Number of buckets used for histogram(CUT).
constexpr uint32_t kBuckets = 40;

// Default id for the histogram.
constexpr uint64_t kMetricId = 1;

// Component name.
constexpr char kComponent[] = "SomeRandomHistogramComponent";

constexpr uint32_t kEventCode = 2;

RemoteMetricInfo MakeRemoteMetricInfo() {
  RemoteMetricInfo metric_info;
  metric_info.metric_id = kMetricId;
  metric_info.component = kComponent;
  metric_info.event_code = kEventCode;
  return metric_info;
}

HistogramOptions MakeHistogramOptions() {
  HistogramOptions options = HistogramOptions::CustomizedExponential(kBuckets, 2, 1, 0);
  options.SetMode(MetricOptions::Mode::kRemote);
  options.metric_id = kMetricId;
  options.component = kComponent;
  options.event_code = kEventCode;
  return options;
}

RemoteHistogram<kBuckets> MakeRemoteHistogram() {
  return RemoteHistogram<kBuckets>(MakeRemoteMetricInfo());
}

bool HistEventValuesEq(const fbl::Vector<HistogramBucket>& actual,
                       const fbl::Vector<HistogramBucket>& expected) {
  BEGIN_HELPER;
  ASSERT_EQ(actual.size(), expected.size());
  for (size_t i = 0; i < actual.size(); ++i) {
    const HistogramBucket& actual_bucket = actual[i];
    bool found = false;
    for (size_t j = 0; j < expected.size(); ++j) {
      const HistogramBucket& expected_bucket = expected[j];
      if (actual_bucket.index != expected_bucket.index) {
        continue;
      }
      EXPECT_EQ(actual_bucket.count, expected_bucket.count);
      found = true;
      break;
    }
    ASSERT_TRUE(found);
  }
  END_HELPER;
}

// Verify the count of the appropiate bucket is updated on increment.
bool TestIncrement() {
  BEGIN_TEST;
  BaseHistogram<kBuckets> histogram;

  // Increase the count of each bucket bucket_index times.
  for (uint32_t bucket_index = 0; bucket_index < kBuckets; ++bucket_index) {
    ASSERT_EQ(histogram.GetCount(bucket_index), 0);
    for (uint32_t times = 0; times < bucket_index; ++times) {
      histogram.IncrementCount(bucket_index);
    }
    ASSERT_EQ(histogram.GetCount(bucket_index), bucket_index);
  }

  // Verify that the operations are isolated, each bucket should have bucket_index counts.
  for (uint32_t bucket_index = 0; bucket_index < kBuckets; ++bucket_index) {
    ASSERT_EQ(histogram.GetCount(bucket_index), bucket_index);
  }
  END_TEST;
}

// Verify the count of the appropiate bucket is updated on increment with a specified value. This
// verifies the behaviour for weighted histograms, where the weight is limited to an integer.
bool TestIncrementByVal() {
  BEGIN_TEST;
  BaseHistogram<kBuckets> histogram;

  // Increase the count of each bucket bucket_index times.
  for (uint32_t bucket_index = 0; bucket_index < kBuckets; ++bucket_index) {
    ASSERT_EQ(histogram.GetCount(bucket_index), 0);
    histogram.IncrementCount(bucket_index, bucket_index);
    ASSERT_EQ(histogram.GetCount(bucket_index), bucket_index);
  }

  // Verify that the operations are isolated, each bucket should have bucket_index counts.
  for (uint32_t bucket_index = 0; bucket_index < kBuckets; ++bucket_index) {
    ASSERT_EQ(histogram.GetCount(bucket_index), bucket_index);
  }
  END_TEST;
}

struct IncrementArgs {
  // Target histogram.
  BaseHistogram<kBuckets>* histogram;

  // Used for signaling the worker thread to start incrementing.
  sync_completion_t* start;

  // Number of times to call Increment.
  size_t operations = 0;
};

// Increment each bucket by 2 * operations * bucket_index.
int IncrementFn(void* args) {
  IncrementArgs* increment_args = static_cast<IncrementArgs*>(args);
  sync_completion_wait(increment_args->start, zx::sec(20).get());

  for (uint32_t bucket = 0; bucket < kBuckets; ++bucket) {
    for (size_t i = 0; i < increment_args->operations; ++i) {
      increment_args->histogram->IncrementCount(bucket, bucket);
    }
    increment_args->histogram->IncrementCount(bucket, bucket * increment_args->operations);
  }

  return thrd_success;
}

// Verifies that calling increment from multiple threads, yields consistent results. Multiple
// threads will call Increment a known number of times, then the total count per bucket should be
// the sum of the times each thread called Increment one each bucket.
bool TestIncrementMultiThread() {
  BEGIN_TEST;
  sync_completion_t start;
  BaseHistogram<kBuckets> histogram;
  fbl::Vector<thrd_t> thread_ids;
  IncrementArgs args[kThreads];

  thread_ids.reserve(kThreads);
  for (uint64_t i = 0; i < kThreads; ++i) {
    thread_ids.push_back({});
  }

  for (uint64_t i = 0; i < kThreads; ++i) {
    auto& thread_id = thread_ids[i];
    args[i].histogram = &histogram;
    args[i].operations = i;
    args[i].start = &start;
    ASSERT_EQ(thrd_create(&thread_id, IncrementFn, &args[i]), thrd_success);
  }

  // Notify threads to start incrementing the count.
  sync_completion_signal(&start);

  // Wait for all threads to finish.
  for (const auto& thread_id : thread_ids) {
    thrd_join(thread_id, nullptr);
  }

  // Each thread increses each bucket by 2 * bucket_index, so the expected amount for
  // each bucket is: 2 * bucket_index * Sum(i=0, kThreads -1) i = 2 * bucket_index *
  // kThreads * (kThreads - 1) / 2;
  constexpr size_t amount = (kThreads - 1) * (kThreads);
  for (uint32_t i = 0; i < kBuckets; ++i) {
    // We take the sum of the accumulated and what is left, because the last
    // increment may have been scheduled after the last flush.
    EXPECT_EQ(histogram.GetCount(i), i * amount);
  }
  END_TEST;
}

bool TestLazyInitialization() {
  BEGIN_TEST;
  FakeLogger logger;
  RemoteHistogram<kBuckets> histogram;
  // Lazily initialize the options.
  histogram.Initialize(MakeHistogramOptions());

  fbl::Vector<HistogramBucket> expected_buckets;
  expected_buckets.reserve(histogram.size());

  // Increase the count of each bucket bucket_index times.
  for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
    ASSERT_EQ(histogram.GetCount(bucket_index), 0);
    histogram.IncrementCount(bucket_index, bucket_index);
    ASSERT_EQ(histogram.GetCount(bucket_index), bucket_index);
    expected_buckets.push_back(
        {.index = static_cast<uint32_t>(bucket_index), .count = bucket_index});
    ASSERT_EQ(expected_buckets[bucket_index].index, bucket_index);
    ASSERT_EQ(expected_buckets[bucket_index].count, bucket_index);
  }

  ASSERT_TRUE(histogram.Flush(&logger));

  ASSERT_EQ(logger.logged_histograms().size(), 1);

  // Check that flushed data is actually what we expect:
  // The metadata is the same, and each bucket contains bucket_index count.
  auto& hist_entry = logger.logged_histograms()[0];

  EXPECT_TRUE(hist_entry.metric_info == MakeRemoteMetricInfo());

  // Verify there is a bucket event_data.
  EXPECT_TRUE(HistEventValuesEq(hist_entry.buckets, expected_buckets));

  // Verify all buckets are 0.
  for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
    EXPECT_EQ(histogram.GetCount(bucket_index), 0);
  }
  END_TEST;
}

// Verifies that when flushing an histogram, all the flushed data matches that of
// the count in the histogram.
bool TestFlush() {
  BEGIN_TEST;
  FakeLogger logger;
  RemoteHistogram<kBuckets> histogram = MakeRemoteHistogram();
  fbl::Vector<HistogramBucket> expected_buckets;
  expected_buckets.reserve(histogram.size());

  // Increase the count of each bucket bucket_index times.
  for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
    ASSERT_EQ(histogram.GetCount(bucket_index), 0);
    histogram.IncrementCount(bucket_index, bucket_index);
    ASSERT_EQ(histogram.GetCount(bucket_index), bucket_index);
    expected_buckets.push_back(
        {.index = static_cast<uint32_t>(bucket_index), .count = bucket_index});
  }

  ASSERT_TRUE(histogram.Flush(&logger));

  ASSERT_FALSE(logger.logged_histograms().is_empty());

  // Check that flushed data is actually what we expect:
  // The metadata is the same, and each bucket contains bucket_index count.
  auto& hist_entry = logger.logged_histograms()[0];

  EXPECT_TRUE(hist_entry.metric_info == MakeRemoteMetricInfo());

  // Verify there is a bucket event_data.
  EXPECT_TRUE(HistEventValuesEq(hist_entry.buckets, expected_buckets));

  // Verify all buckets are 0.
  for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
    EXPECT_EQ(histogram.GetCount(bucket_index), 0);
  }
  END_TEST;
}

struct FlushArgs {
  // Pointer to the histogram which is flushing incremental snapshot to a
  // 'remote' histogram.
  RemoteHistogram<kBuckets>* histogram;

  // Pointer to the 'Remote' histogram that is accumulating the data of each
  // flush.
  BaseHistogram<kBuckets>* accumulated_histogram;

  // Used to enforce the threads start together. The main thread will signal
  // after all threads have been started.
  sync_completion_t* start;

  // Mutex used as a barrier for Flush to prevent any overlapping.
  fbl::Mutex* flush_mutex = nullptr;

  // Number of times to perform the given operation.
  size_t operations = 0;

  // Whether the thread will flush, if flase it will be incrementing the buckets.
  bool flush = false;
};

int FlushFn(void* args) {
  FlushArgs* flush_args = static_cast<FlushArgs*>(args);

  sync_completion_wait(flush_args->start, zx::sec(20).get());
  FakeLogger logger;

  for (size_t i = 0; i < flush_args->operations; ++i) {
    if (flush_args->flush) {
      fbl::AutoLock lock(flush_args->flush_mutex);
      flush_args->histogram->Flush(&logger);
    } else {
      for (uint32_t j = 0; j < kBuckets; ++j) {
        flush_args->histogram->IncrementCount(j, j);
      }
    }
  }

  for (auto& hist_entry : logger.logged_histograms()) {
    for (auto& bucket_entry : hist_entry.buckets) {
      flush_args->accumulated_histogram->IncrementCount(bucket_entry.index, bucket_entry.count);
    }
  }

  return thrd_success;
}

// Verify that under concurrent environment the final results are consistent. This
// test will have |kThreads|/2 threads increment bucket counts, and |kThreads|/2
// Flush them a certain amount of times, and collect into a BaseHistogram the final
// results. At the end, each bucket of the BaseHistogram should be the expected
// value.
bool TestFlushMultithread() {
  BEGIN_TEST;
  sync_completion_t start;
  BaseHistogram<kBuckets> accumulated;
  RemoteHistogram<kBuckets> histogram = MakeRemoteHistogram();
  fbl::Vector<thrd_t> thread_ids;
  fbl::Mutex flush_mutex;
  FlushArgs args[kThreads];

  thread_ids.reserve(kThreads);
  for (uint64_t i = 0; i < kThreads; ++i) {
    thread_ids.push_back({});
  }

  for (uint64_t i = 0; i < kThreads; ++i) {
    auto& thread_id = thread_ids[i];
    args[i].histogram = &histogram;
    args[i].accumulated_histogram = &accumulated;
    args[i].operations = i;
    args[i].flush = i % 2;
    args[i].start = &start;
    args[i].flush_mutex = &flush_mutex;
    ASSERT_EQ(thrd_create(&thread_id, FlushFn, &args[i]), thrd_success);
  }

  // Notify threads to start incrementing the count.
  sync_completion_signal(&start);

  // Wait for all threads to finish.
  for (const auto& thread_id : thread_ids) {
    thrd_join(thread_id, nullptr);
  }

  // Each thread at an even position, increases the the count of a bucket by
  // bucket_index.
  constexpr size_t ceil_threads = ((kThreads - 1) / 2 * ((kThreads - 1) / 2 + 1));
  for (uint32_t i = 0; i < kBuckets; ++i) {
    // We take the sum of the accumulated and what is left, because the last
    // increment may have been scheduled after the last flush.
    EXPECT_EQ(accumulated.GetCount(i) + histogram.GetCount(i), i * ceil_threads);
  }
  END_TEST;
}

bool TestHistogramLazyInitialization() {
  BEGIN_TEST;
  // Buckets 2^i + offset.
  Histogram<kBuckets> histogram;
  histogram.Initialize(MakeHistogramOptions(), nullptr);

  histogram.Add(25);
  ASSERT_EQ(histogram.GetRemoteCount(25), 1);
  histogram.Add(25, 4);
  histogram.Add(1500, 2);

  ASSERT_EQ(histogram.GetRemoteCount(25), 5);
  ASSERT_EQ(histogram.GetRemoteCount(1500), 2);

  END_TEST;
}

bool TestAdd() {
  BEGIN_TEST;
  // Buckets 2^i + offset.
  Histogram<kBuckets> histogram(MakeHistogramOptions());

  histogram.Add(25);
  ASSERT_EQ(histogram.GetRemoteCount(25), 1);
  histogram.Add(25, 4);
  histogram.Add(1500, 2);

  ASSERT_EQ(histogram.GetRemoteCount(25), 5);
  ASSERT_EQ(histogram.GetRemoteCount(1500), 2);

  END_TEST;
}

// Verify that from the public point of view, changes are reflected accurately,
// while internally the buckets are accessed correctly.
//
// Note: The two extra buckets, are for underflow and overflow buckets.
bool TestAddMultiple() {
  BEGIN_TEST;
  HistogramOptions options = MakeHistogramOptions();
  Histogram<kBuckets> histogram(options);
  // Histogram internally allocates an overflow and underflow bucket.
  BaseHistogram<kBuckets + 2> expected_hist;

  struct ValueBucket {
    double value;
    uint32_t bucket;
  };
  fbl::Vector<ValueBucket> data;
  unsigned int seed = static_cast<unsigned int>(zx::ticks::now().get());

  // 500 random observation.
  for (int i = 0; i < 500; ++i) {
    ValueBucket curr;
    curr.bucket = rand_r(&seed) % (histogram.size());
    double min;
    double max;
    if (curr.bucket == 0) {
      min = -DBL_MAX;
    } else {
      min = options.reverse_map_fn(curr.bucket, histogram.size(), options);
    }
    max = nextafter(options.reverse_map_fn(curr.bucket + 1, histogram.size(), options), min);
    curr.value = min + (max - min) * (static_cast<double>(rand_r(&seed)) / RAND_MAX);
    ASSERT_EQ(options.map_fn(curr.value, histogram.size(), options), curr.bucket);

    Histogram<kBuckets>::Count count = 1 + rand_r(&seed) % 20;
    expected_hist.IncrementCount(curr.bucket, count);
    histogram.Add(curr.value, count);
  }

  // Verify that the data stored through public API, matches the expected values.
  for (auto& val_bucket : data) {
    EXPECT_EQ(histogram.GetRemoteCount(val_bucket.value),
              expected_hist.GetCount(val_bucket.bucket));
  }

  END_TEST;
}

// Verify we are always exposing the delta since last FlushFn.
bool TestAddAfterFlush() {
  BEGIN_TEST;
  // Buckets 2^i + offset.
  HistogramOptions options = HistogramOptions::CustomizedExponential(/*bucket_count=*/kBuckets,
                                                                     /*base=*/2,
                                                                     /*scalar=*/1, /*offset=*/-10);
  options.SetMode(MetricOptions::Mode::kRemote);
  internal::FlushInterface* remote_histogram;
  FakeLogger logger;
  Histogram<kBuckets> histogram(options, &remote_histogram);
  BaseHistogram<kBuckets + 2> expected_hist;

  histogram.Add(25, 4);
  ASSERT_EQ(histogram.GetRemoteCount(25), 4);
  remote_histogram->Flush(&logger);
  histogram.Add(25, 4);
  histogram.Add(1500, 2);

  ASSERT_EQ(histogram.GetRemoteCount(25), 4);
  ASSERT_EQ(histogram.GetRemoteCount(1500), 2);

  END_TEST;
}

struct Observation {
  double value;
  Histogram<kBuckets>::Count count;
};

struct HistogramFnArgs {
  // Public histogram which is used to add observations.
  Histogram<kBuckets>* histogram;

  // When we are flushing we act as the collector, so we need
  // a pointer to the underlying histogram.
  FlushInterface* remote_histogram = nullptr;

  // We flush the contents at each step into this histogram.
  BaseHistogram<kBuckets + 2>* flushed_histogram = nullptr;

  // Synchronize thread start.
  sync_completion_t* start = nullptr;

  // Used as a barrier for preventing concurrent Flushes.
  fbl::Mutex* flush_mutex = nullptr;

  // Observations each thread will add.
  fbl::Vector<Observation>* observed_values = nullptr;

  // The thread will flush contents if set to true.
  bool flush = false;
};

// Wait until all threads are started, then start adding observations or
// flushing, depending  on the thread parameters.
int HistogramFn(void* v_args) {
  HistogramFnArgs* args = reinterpret_cast<HistogramFnArgs*>(v_args);
  FakeLogger logger;
  sync_completion_wait(args->start, zx::sec(20).get());
  for (auto& obs : *args->observed_values) {
    if (!args->flush) {
      args->histogram->Add(obs.value, obs.count);
    } else {
      fbl::AutoLock lock(args->flush_mutex);
      args->remote_histogram->Flush(&logger);
    }
  }

  for (auto& hist_entry : logger.logged_histograms()) {
    for (auto& bucket_entry : hist_entry.buckets) {
      args->flushed_histogram->IncrementCount(bucket_entry.index, bucket_entry.count);
    }
  }

  return thrd_success;
}

// Verify that when multiple threads call Add the result is eventually consistent, meaning that the
// total count in each bucket should match the count in an histogram that is being kept
// manually(BaseHistogram)
bool TestAddMultiThread() {
  BEGIN_TEST;
  // Buckets 2^i + offset.
  FlushInterface* remote_histogram;
  HistogramOptions options = MakeHistogramOptions();
  Histogram<kBuckets> histogram(options, &remote_histogram);
  BaseHistogram<kBuckets + 2> expected_hist;
  fbl::Vector<Observation> observations;

  // 1500 random observation.
  unsigned int seed = static_cast<unsigned int>(zx::ticks::now().get());
  for (int i = 0; i < 1500; ++i) {
    Observation obs;
    uint32_t bucket = rand_r(&seed) % (kBuckets + 2);
    double min;
    double max;
    if (bucket == 0) {
      min = -DBL_MAX;
    } else {
      min = options.reverse_map_fn(bucket, histogram.size(), options);
    }
    max = nextafter(options.reverse_map_fn(bucket + 1, histogram.size(), options), min);
    obs.value = min + (max - min) * (static_cast<double>(rand_r(&seed)) / RAND_MAX);
    ASSERT_EQ(options.map_fn(obs.value, histogram.size(), options), bucket);
    obs.count = 1 + rand_r(&seed) % 20;
    expected_hist.IncrementCount(bucket, kThreads * obs.count);
    observations.push_back(obs);
  }

  // Thread data.
  HistogramFnArgs args;
  sync_completion_t start;
  fbl::Vector<thrd_t> thread_ids;
  args.histogram = &histogram;
  args.start = &start;
  args.observed_values = &observations;

  for (size_t thread = 0; thread < kThreads; ++thread) {
    thread_ids.push_back({});
    auto& thread_id = thread_ids[thread];
    ASSERT_EQ(thrd_create(&thread_id, HistogramFn, &args), thrd_success);
  }
  sync_completion_signal(&start);

  for (auto& thread_id : thread_ids) {
    thrd_join(thread_id, nullptr);
  }

  // Verify each bucket has the exact value as the expected histogram,
  for (uint32_t bucket = 0; bucket < kBuckets + 2; ++bucket) {
    double value;
    value = options.reverse_map_fn(bucket, histogram.size(), options);
    EXPECT_EQ(histogram.GetRemoteCount(value), expected_hist.GetCount(bucket));
  }
  END_TEST;
}

// Verify that when multiple threads call Add and Flush consistently, the result is eventually
// consistent, meaning that for each bucket, the amount in expected_hist is equal to what remains
// in the remote histogram plus the amount in the flushed hist. Essentially a sanity check that
// data is not lost.
bool TestAddAndFlushMultiThread() {
  BEGIN_TEST;
  // Buckets 2^i + offset.
  HistogramOptions options = MakeHistogramOptions();
  FlushInterface* remote_histogram;
  Histogram<kBuckets> histogram(options, &remote_histogram);
  fbl::Vector<Observation> observations;
  fbl::Mutex flush_mutex;
  BaseHistogram<kBuckets + 2> expected_hist, flushed_hist;

  // 1500 random observation.
  unsigned int seed = static_cast<unsigned int>(zx::ticks::now().get());
  for (int i = 0; i < 1500; ++i) {
    Observation obs;
    uint32_t bucket = rand_r(&seed) % (kBuckets + 2);
    double min;
    double max;
    if (bucket == 0) {
      min = -DBL_MAX;
    } else {
      min = options.reverse_map_fn(bucket, histogram.size(), options);
    }
    max = nextafter(options.reverse_map_fn(bucket + 1, histogram.size(), options), min);
    obs.value = min + (max - min) * (static_cast<double>(rand_r(&seed)) / RAND_MAX);
    ASSERT_EQ(options.map_fn(obs.value, histogram.size(), options), bucket);
    obs.count = 1 + rand_r(&seed) % 20;
    expected_hist.IncrementCount(bucket, (kThreads / 2 + kThreads % 2) * obs.count);
    observations.push_back(obs);
  }

  sync_completion_t start;
  HistogramFnArgs add_args;
  fbl::Vector<thrd_t> thread_ids;
  add_args.start = &start;
  add_args.histogram = &histogram;
  add_args.observed_values = &observations;

  HistogramFnArgs flush_args = add_args;
  flush_args.flush = true;
  flush_args.flushed_histogram = &flushed_hist;
  flush_args.remote_histogram = remote_histogram;
  flush_args.flush_mutex = &flush_mutex;

  for (size_t thread = 0; thread < kThreads; ++thread) {
    thread_ids.push_back({});
    auto& thread_id = thread_ids[thread];
    if (thread % 2 != 0) {
      ASSERT_EQ(thrd_create(&thread_id, HistogramFn, &add_args), thrd_success);
    } else {
      ASSERT_EQ(thrd_create(&thread_id, HistogramFn, &flush_args), thrd_success);
    }
  }

  sync_completion_signal(&start);

  for (auto& thread_id : thread_ids) {
    thrd_join(thread_id, nullptr);
  }

  // Verify each bucket has the exact value as the expected histogram. The addition here, is just
  // because we have no guarantee that the last flush happened after the last add. Essentially
  // what we have not yet flushed, plus what we flushed, should be equal to the expected value if
  // we didnt flush at all.
  for (uint32_t bucket = 0; bucket < histogram.size(); ++bucket) {
    double value;
    value = options.reverse_map_fn(bucket, histogram.size(), options);
    EXPECT_EQ(histogram.GetRemoteCount(value) + flushed_hist.GetCount(bucket),
              expected_hist.GetCount(bucket));
  }
  END_TEST;
}

BEGIN_TEST_CASE(BaseHistogramTest)
RUN_TEST(TestIncrement)
RUN_TEST(TestIncrementByVal)
RUN_TEST(TestIncrementMultiThread)
END_TEST_CASE(BaseHistogramTest)

BEGIN_TEST_CASE(RemoteHistogramTest)
RUN_TEST(TestFlush)
RUN_TEST(TestFlushMultithread)
RUN_TEST(TestLazyInitialization)
END_TEST_CASE(RemoteHistogramTest)

BEGIN_TEST_CASE(HistogramTest)
RUN_TEST(TestHistogramLazyInitialization)
RUN_TEST(TestAdd)
RUN_TEST(TestAddAfterFlush)
RUN_TEST(TestAddMultiple)
RUN_TEST(TestAddMultiThread)

// Test disabled due to flakiness. See FLK-329.
// RUN_TEST(TestAddAndFlushMultiThread)

END_TEST_CASE(HistogramTest)

}  // namespace
}  // namespace internal
}  // namespace cobalt_client
